﻿using Microsoft.AspNetCore.SignalR;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;

using PLCS.Domain.Logs;
using PLCS.Domain.Shared.PLCConfigs;
using PLCS.Domain.Shared.Tasks;
using PLCS.Domain.Tasks;

using System.Diagnostics;
using System.Text;

namespace PLCS.PlcInteraction;

[DisallowConcurrentExecution] // 禁止并行操作特性
internal class PLCReadWorker : QuartzBackgroundWorkerBase
{
    private readonly ILogger<PLCReadWorker> _logger;
    private readonly IPLCReadDataRepository _plcReadDataRepository;
    private readonly IPLCConfigRepository _plcConfigRepository;
    private readonly IConfiguration _configuration;
    private readonly ErrorLogManager _errorLogRepository;
    private static Dictionary<string, S7Client> plcReadClients;
    private static IEnumerable<PLCConfig> plcConfigs;

    public PLCReadWorker(ILogger<PLCReadWorker> logger, IPLCReadDataRepository plcReadDataRepository, IPLCConfigRepository plcConfigRepository, IConfiguration configuration, ErrorLogManager errorLogManager)
    {
        JobDetail = JobBuilder.Create<PLCReadWorker>().WithIdentity(nameof(PLCReadWorker)).Build();
        Trigger = TriggerBuilder.Create().WithIdentity(nameof(PLCReadWorker))
            .WithSimpleSchedule(s => s.WithIntervalInSeconds(1)
            .RepeatForever()
            .WithMisfireHandlingInstructionIgnoreMisfires())
            .Build();

        ScheduleJob = async scheduler =>
        {
            if (!await scheduler.CheckExists(JobDetail.Key))
            {
                await scheduler.ScheduleJob(JobDetail, Trigger);
            }
        };
        _logger = logger;
        _plcReadDataRepository = plcReadDataRepository;
        _plcConfigRepository = plcConfigRepository;
        _configuration = configuration;
        _errorLogRepository = errorLogManager;
        plcReadClients = new Dictionary<string, S7Client>();
    }

    public override async Task Execute(IJobExecutionContext context)
    {
        await Orientationver();
        //完成信号和堆垛机当前列需要加入
    }
    public async Task Orientationver()
    {
        var stopwatch = Stopwatch.StartNew();
        // 获取正在使用的PLC配置列表
        plcConfigs ??= await _plcConfigRepository.GetListInUseWithDetailsAsync();
        // 将遍历操作改为并行操作，在多个线程中同时执行读取操作，从而提高效率
        var res = await Task.WhenAll(plcConfigs.Where(x => x.IPAddress != "192.168.0.1").Select(item => PLCReadAsync(item)));
        var waitUpdateList = res.SelectMany(item => item).ToList();
        if (waitUpdateList.Any())
            await HandleUpdateValueAsync(waitUpdateList.ToList());

        stopwatch.Stop();
        var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
        _logger.LogInformation($"PLC读取执行总时间：{elapsedSeconds}秒");
    }

    /// <summary>
    /// 异步方法，用于从PLC中读取数据。
    /// </summary>
    /// <param name="client">S7Client对象，用于连接PLC</param>
    /// <param name="pLC_ConfigInfo">PLC配置对象，包含连接PLC所需的参数</param>
    private async Task<IEnumerable<PLCReadData>> PLCReadAsync(PLCConfig pLC_ConfigInfo)
    {
        try
        {
            IEnumerable<PLCReadData> waitUpdateList = new List<PLCReadData>();
            var client = plcReadClients.GetOrAdd(pLC_ConfigInfo.IPAddress, x => new S7Client());
            if (!client.Connected)
            {
                // 尝试连接PLC
                int result = client.ConnectTo(pLC_ConfigInfo.IPAddress, pLC_ConfigInfo.Rack, pLC_ConfigInfo.Slot);
                if (result == 0)
                {
                    _logger.LogInformation($"{pLC_ConfigInfo.IPAddress} PLC连接成功");
                }
                else
                {
                    await _errorLogRepository.AddErrorLogAsync(new ErrorLog(result.ToString(), $"{pLC_ConfigInfo.IPAddress} PLC连接失败，错误码：{result}，错误信息：{client.ErrorText(result)}。", "PLC通讯", "通讯"));
                    _logger.LogError($"{pLC_ConfigInfo.IPAddress} PLC连接失败，错误码：{result}，错误信息：{client.ErrorText(result)}。");
                    return waitUpdateList;
                }
            }
            var stopwatch = Stopwatch.StartNew();
            // 读取数据
            byte[] buffer = new byte[pLC_ConfigInfo.Length + 1];
            int read = client.DBRead(pLC_ConfigInfo.DBNumber, 0, pLC_ConfigInfo.Length, buffer);
            if (read == 0)
            {
                stopwatch.Stop();
                var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
                _logger.LogInformation($"‘{pLC_ConfigInfo.IPAddress}’读取DB成功，长度：{pLC_ConfigInfo.Length}，执行总时间：{elapsedSeconds}秒");
                //// 处理数据
                waitUpdateList = ProcessPlcReadData(buffer, pLC_ConfigInfo.PLCReadDatas);
                //if (waitUpdateList.Any())
                //{
                //    await HandleUpdateValueAsync(waitUpdateList.ToList());
                //}
                return waitUpdateList;
            }
            else
            {
                await _errorLogRepository.AddErrorLogAsync(new ErrorLog(read.ToString(), $"‘{pLC_ConfigInfo.IPAddress}’PLC字段读取异常，错误码：{read}，错误信息：{client.ErrorText(read)}。", "PLC通讯", "通讯"));
                _logger.LogWarning($"‘{pLC_ConfigInfo.IPAddress}’PLC字段读取异常，错误码：{read}，错误信息：{client.ErrorText(read)}。");
                return waitUpdateList;
            }
        }
        catch (Exception ex)
        {
            plcReadClients.Remove(pLC_ConfigInfo.IPAddress);
            _logger.LogError($"‘{pLC_ConfigInfo.IPAddress}’PLCReadAsync方法异常，异常信息：{ex.Message}");
            return new List<PLCReadData>();
        }
    }

    #region 处理从PLC读取到的数据，更新数据库中的值

    /// <summary>
    /// 处理从PLC读取到的数据，更新数据库中的值
    /// </summary>
    /// <param name="buffer">从PLC读取到的原始字节流数据</param>
    /// <param name="readDataList">需要处理的PLC读取数据列表</param>
    private IEnumerable<PLCReadData> ProcessPlcReadData(byte[] buffer, IEnumerable<PLCReadData>? readDataList)
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            var waitUpdateList = new List<PLCReadData>();
            if (readDataList == null)
                return waitUpdateList;
            foreach (var item in readDataList)
            {
                var pLC_ReadData = item;

                if (item.StartBit > buffer.Length - item.Length)
                    continue;
                // 获取数据类型处理方法的委托
                var dataTypeHandler = PLCReader.GetDataTypeHandler(pLC_ReadData.DataType);

                // 调用数据类型处理方法，获取值
                var value = dataTypeHandler(buffer, pLC_ReadData.StartBit, pLC_ReadData.EndBit, pLC_ReadData.Length);

                if (value == null)
                    continue;

                string? readStringValue = value.ToString();

                if (readStringValue == null || item.CurrentValue == readStringValue)
                    continue;

                // 更新数据库中的值
                item.ChangeValue(readStringValue);
                waitUpdateList.Add(item);
            }
            stopwatch.Stop();
            var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
            _logger.LogInformation($"ProcessPlcReadData执行总时间：{elapsedSeconds}秒");
            return waitUpdateList;
        }
        catch (Exception ex)
        {
            _logger.LogError($"ProcessPlcReadDataAsync方法异常，异常信息：{ex.Message}");
            return new List<PLCReadData>();
        }
    }

    private async Task HandleUpdateValueAsync(List<PLCReadData> waitUpdateList)
    {
        var stopwatch1 = Stopwatch.StartNew();
        StringBuilder sqlBuilder = new StringBuilder();
        foreach (var item in waitUpdateList)
        {
            var correspondingField = item.CorrespondingField;
            var correspondingTable = item.CorrespondingTable;
            var whereField = item.WhereField;
            var whereValue = item.WhereValue;
            var value = item.CurrentValue;
            if (string.IsNullOrEmpty(correspondingField)
                || string.IsNullOrEmpty(correspondingTable)
                || string.IsNullOrEmpty(whereField)
                || string.IsNullOrEmpty(whereValue)
                || string.IsNullOrEmpty(value))
            {
                continue;
            }

            sqlBuilder.AppendLine($"update {correspondingTable} set {correspondingField} = '{value}' where {whereField} = '{whereValue}'");
        }

        string sql = sqlBuilder.ToString().Trim();

        if (!string.IsNullOrEmpty(sql))
        {
            using SqlConnection conn = new SqlConnection(_configuration.GetConnectionString("default"));
            await conn.OpenAsync();

            using SqlTransaction transaction = conn.BeginTransaction();
            try
            {
                using (SqlCommand cmd = new SqlCommand(sql, conn, transaction))
                {
                    var res = await cmd.ExecuteNonQueryAsync();
                    stopwatch1.Stop();
                    var elapsedSeconds = stopwatch1.Elapsed.TotalSeconds;
                    _logger.LogInformation($"共有{res}条语句同步映射，sql语句为：{sql}，修改语句为：{cmd.CommandText}，修改时长为{elapsedSeconds}秒");
                    if (res == 0)
                    {
                        _logger.LogWarning($"HandleUpdateValue事件处理异常,修改行数为0，修改语句为：{cmd.CommandText}，修改时长为{elapsedSeconds}秒");
                    }
                }

                transaction.Commit();
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                _logger.LogError($"HandleUpdateValueAsync事件处理异常：{ex.Message}");
                return;
            }
        }

        try
        {
            var stopwatch = Stopwatch.StartNew();
            await _plcReadDataRepository.UpdateManyOnlyCurrentValueAsync(waitUpdateList);
            stopwatch.Stop();
            var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
            _logger.LogInformation($"PLCReadData字段修改成功，字段：{waitUpdateList.Select(x => x.PLCReadDataNo).JoinAsString(",")},修改时长：{elapsedSeconds}秒");
        }
        catch (Exception ex)
        {
            _logger.LogError($"PLCReadData字段修改失败,失败字段：{waitUpdateList.Select(x => x.PLCReadDataNo).JoinAsString(",")}，失败原因：{ex.Message}");
        }
    }

    #endregion 处理从PLC读取到的数据，更新数据库中的值
}